Skip to content

Add noise generators and improve their distribution #755

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 4, 2025

Conversation

roderickvd
Copy link
Collaborator

@roderickvd roderickvd commented Jun 13, 2025

Fixes

The current WhiteNoise generator has a problem where its distribution is not at all in range of [-1.0, 1.0] due to precision loss, truncation and bias to zero from doing this:

let rand = self.rng.next_u32() as f32 / u32::MAX as f32;
let scaled = rand * 2.0 - 1.0;

This PR fixes that by using a proper f32 generated uniform distribution.

Then the current PinkNoise builds on top of the incorrect WhiteNoise but worse: has coefficients that are valid for 44.1 kHz only. This PR fixes that by algorithmically generating proper pink noise.

Finally, try_seek should provide deterministic seeking (same results after seeking) and PinkNoise cannot provide that. This PR correctly implements try_seek for noise generators that are deterministic (or uniformly random).

New noise generators

Threw in some freebies:

  • Gaussian white noise
  • Triangular white noise
  • Blue noise
  • Pink noise
  • Brownian noise
  • Velvet noise

Including documentation on when you'd want to use which.

I noticed that docs.rs didn't document the feature-gated noise generators, so I've set it to generate documentation for all features.

Advanced construction options

The current API remains as-is: source::white() will give you a WhiteGenerator<SmallRng>. But you see that I made the generators generic for more choice to the user: source::WhiteNoise::<R: Rng>::new{_with_seed}().

This now provides a rather complete suite of high-quality noise generators for audio synthesis, testing, and dithering.

@dvdsk
Copy link
Collaborator

dvdsk commented Jun 14, 2025

Finally, try_seek should provide deterministic seeking (same results after seeking)

should it? I've never seen seek as reproducing something rather navigating in an underlying stream. I would say the seek implementation for noise like this should do nothing. But if we do go on with seek in noise -> error then we should provide a wrapper that "eats" the seek operation. Basically:

struct DisableSeek;

impl Source for DisableSeek {
    ....
    
    fn try_seek() -> Result<() , _> {
        Ok(())
    }
}

Copy link
Collaborator

@dvdsk dvdsk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did not have time to look at the implementation in depth I do have some feedback/questions regarding the API and tests.

@roderickvd
Copy link
Collaborator Author

Finally, try_seek should provide deterministic seeking (same results after seeking)

should it? I've never seen seek as reproducing something rather navigating in an underlying stream. I would say the seek implementation for noise like this should do nothing. But if we do go on with seek in noise -> error then we should provide a wrapper that "eats" the seek operation.

Here is an example of what would happen if we would "eat" seeks:

// Create with a fixed seed for determinism
let mut noise = pink::new_with_seed(44100, 12345);
// Play for a while, changing internal state
for _ in 0..1000 { noise.next(); }
// Rewind to the beginning
noise.try_seek(Duration::from_secs(0)).unwrap();

// Create another generator with the same seed
let mut noise2 = pink::new_with_seed(44100, 12345);
// Noises should be the same, but are not
assert_eq!(noise.next(), noise2.next());

...where you've got me however is that this is also true for my proposed implementation of WhiteNoise. We can get around that by storing/resetting original seeds, but I'm not sure about the added value vs. added complexity.

Thinking out loud: maybe we should remove _with_seed altogether and prevent illusions of determinism.

@dvdsk
Copy link
Collaborator

dvdsk commented Jun 14, 2025

Thinking out loud: maybe we should remove _with_seed altogether and prevent illusions of determinism.

I think a deterministic noise gen is pretty neat but I struggle to think of a use case for it. Noise pretty much sounds the same regardless of seed right?

If we can not find a good use case now I would propose we scrap determinism. We can always add deterministic_noise later that goes through all kinds of complex slow tricks to stay perfectly deterministic.

@roderickvd
Copy link
Collaborator Author

If we can not find a good use case now I would propose we scrap determinism. We can always add deterministic_noise later that goes through all kinds of complex slow tricks to stay perfectly deterministic.

Yes, let's scrap it.

The commit improves and expands the noise generation capabilities by adding new
types and fixing distribution issues in existing ones. Key changes:

- Add more generators: Gaussian, triangular, blue, brownian, violet, velvet
- Fix white noise uniform distribution
- Fix pink noise sampling rate issues
- Make noise generators properly deterministic with seeds
- Add comprehensive tests for noise generator properties
- Improve documentation with detailed usage guidance

This provides a complete suite of high-quality noise generators for audio
synthesis, testing, and dithering applications.
- Rename noise generators to PascalCase types (e.g. WhiteUniform, Pink)
- Move all noise generators under `source::noise` module
- Deprecate `white()` and `pink()` in favor of `noise::WhiteUniform::new()` and `noise::Pink::new()`
- Remove legacy noise generator functions/types from prelude
- Update `rstest`, `rstest_reuse` to fix templates in unit tests
- Refactor and expand noise generator documentation and examples
- Move and consolidate noise generator tests into `src/source/noise.rs`
- Update `examples/noise_generator.rs` to use new API and add descriptions
@roderickvd roderickvd force-pushed the feat/more-and-better-noise branch from 94f0f53 to 377fbb9 Compare July 3, 2025 21:19
@roderickvd roderickvd requested a review from dvdsk July 4, 2025 19:53
@dvdsk
Copy link
Collaborator

dvdsk commented Jul 4, 2025

Thanks for all the hard work, it looks really nice.

Only thing I'm still unsure about is the seeking behavior. You've explained you want seeking to be reproducible/deterministic. I think we currently use seeking mostly for decoders. Effects just forward the seek or adjust it in the case of speed. Seeking in decoders is not 100% accurate but we find that okay. Isn't it strange to then disable seeking as soon as a piece of audio gets mixed with noise?

@roderickvd
Copy link
Collaborator Author

Isn't it strange to then disable seeking as soon as a piece of audio gets mixed with noise?

[emphasis mine]

Are you referring to Mix specifically? Because Mix::try_seek currently is always non-seekable regardless of its underlying sources.

But yes, you're probably right to not be too academic about this. It's probably OK to "eat" the seek and always return Ok(()) as any generator will continue to produce continuous noise as long as we don't reset the state.

@dvdsk
Copy link
Collaborator

dvdsk commented Jul 4, 2025

Are you referring to Mix specifically? Because Mix::try_seek currently is always non-seekable regardless of its underlying sources.

your right! In my head that was already fixed. Well we should be able to fix that now that we have TrackPosition.

- Merge basic and seekable noise source macros into `impl_noise_source`
- Simplify documentation and trait implementations for all noise generators
- Update tests to reflect unified seeking support
@roderickvd
Copy link
Collaborator Author

roderickvd commented Jul 4, 2025

OK, a952f97 should do it, making all noise generators seekable. Once you agree, I can squash merge.

@dvdsk
Copy link
Collaborator

dvdsk commented Jul 4, 2025

I think this epic is now done :)

Lets get it merged! I'll see if I can finish up get 0.21 ready for release tomorrow.

@roderickvd roderickvd merged commit 8974f23 into master Jul 4, 2025
9 checks passed
@roderickvd roderickvd deleted the feat/more-and-better-noise branch July 4, 2025 20:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants